package org.reborn.engine.model.mesh

import org.reborn.engine.enums.ArrayType
import org.reborn.engine.enums.FaceMode._

import org.reborn.math.VectorF
import org.reborn.math.Plane3F
import org.reborn.math.util._

import java.nio.FloatBuffer
import java.nio.IntBuffer
 

/**
 * Describes a Float buffer of vertex information.  The type of vertex information 
 * is given by the ArrayType enumeration.  This class is primarily used in the
 * Mesh class.
 */
trait VertexData{
  
  /** The type of information that is stored in an instance of this VertexData class */
  def aType():ArrayType.ArrayType;
  
  /** The number of Float values that are used to describe one vertex in the buffer. */
  def partsPerVertex():Int;
  
  /** The number of vertices that are stored in this VertexData object */
  def verts():Int;
  
  /** The total number of Floats that are stored in the VertexData.  This is equivalent
   *  to partsPerVertex() * verts()  */
  def arraySize():Int;
  
  /** Returns data as an Array of Float values */
  def getDataArray():Array[Float];
  
  /** Returns the data as a FloatBuffer */
  def getDataBuffer():FloatBuffer;
  
  /** Retrieves the Float values at the specified position */
  def apply(pos:Int):Float;
  
  /** Revrieves the vertex at the specified position and returns it as a VectorF with
   *  a length = partsPerVertex */
  def getVertexAt(vertIndex:Int):VectorF;
}


/** Represents the raw vertex and face data for a 3D model.  Type of mesh is given
 *  by FaceMode.
 */
trait Mesh{
	
  /** Returnes a list of all the VertexData types that are stored in the mesh */
  def getAvailableArrayTypes():Iterable[ArrayType.ArrayType];
  
  /** Returnes true if the specified ArrayType exists for this mesh, false otherwise */
  def hasType(arrayType:ArrayType.ArrayType):Boolean;
  
  /** Returns the VertexData for the specified ArrayType */
  def apply(arrayType:ArrayType.ArrayType):VertexData;
  
  /** Returns the face data as an Array of Ints */
  def getFacesArray():Array[Int];
  
  /** Returns the face data as an IntBuffer */
  def getFacesBuffer():IntBuffer;
  
  /** The FaceMode of this Mesh */
  def getFaceMode():FaceMode
  
  /** The number of vertices in this Mesh */
  def verts():Int;
  
  /** The number of faces in this Mesh */
  def faces():Int;
  
  /** The number of vertices that make up a face */
  def verticesPerFace():Int;
  
  /** Given an vertex index from the face Index Array, return
      the real index in the vertex array. */
  protected def getTranslatedVertexIndex(faceIndex:Int):Int;
  
  /** Returns the Vertex at the specified vertex index. */
  def getVertexAt(vertexIndex:Int):Vertex = {
    val translatedIndex = getTranslatedVertexIndex(vertexIndex)
    var vertMap:Map[ArrayType.ArrayType,VectorF] = Map()
    for(aType <- getAvailableArrayTypes) 
      vertMap += ( (aType,apply(aType).getVertexAt(translatedIndex)) )
    
    new Vertex(vertMap)
  }
  
  /** Returns the Face at the specified face index. */
  def getFaceAt(faceIndex:Int):Polygon = {
    val indexStart = faceIndex * verticesPerFace
	var vertList:List[Vertex] = Nil
    for(i <- indexStart until (indexStart + verticesPerFace) ) 
      vertList = getVertexAt(i) :: vertList
    
	new Polygon(vertList.reverse)
  }
  
  def isValidMesh():(Boolean,String) = {
	if(!hasType(ArrayType.Vertex)) 
		return (false,"Mesh does not contain any vertex data.");
	
	val vertData = apply(ArrayType.Vertex)
	val verts = vertData.verts
	if(!getAvailableArrayTypes.forall(apply(_).verts == verts)) 
		return (false,"Vertex data is not the same length for all array types.");
	
	val faceData = getFacesArray
	for(i <- 0 until faces){
		if(faceData(i) < 0 || faceData(i) > verts)
			return (false, "Face index "+i+" does not specify a valid vertex. Found "+faceData(i)+", valid range = [0,"+verts+")")
	}
	
	(true,"Valid Mesh");
  }
  
}

/**
 * Class that represents a single Vertex in a Mesh.  This class is a more 
 * OO representation of a vertex, but it is slower than the Mesh and VertexData
 * representations. It is used mainly in simplifying Mesh operations
 */
class Vertex(val data:Map[ArrayType.ArrayType,VectorF]){
  
  /** Returns a Vertex that is at the intersection of the specified plane,
   *  and the line described by this Vertex and the other Vertex.  The
   *  resulting Vertex and all the data described by it is linearly 
   *  interpolated along the distance from this Vertex to o that the
   *  intersection occurs.
   */
  def intersect(o:Vertex,p:Plane3F):Vertex = {
    require(data.keySet.subsetOf(o.data.keySet))
    
    val point1 = data(ArrayType.Vertex)
    val point2 = o.data(ArrayType.Vertex)
    val dist = MathUtils.intersectionDist(point1,point2,p)
    
    new Vertex(
      Map() ++( data.keySet.map((aType) =>{(aType,(o.data(aType) - data(aType)) * dist + data(aType))}) )
    )
  } 
  
  /** Returns the VectorF for the specified ArrayType */
  def apply(aType:ArrayType.ArrayType):VectorF = data(aType)
  
  override def equals(o:Any):Boolean = {
    o match{
      case v:Vertex => data == v.data
      case _ => false    }
  }
  
  override def hashCode():Int = data.hashCode
  
  override def toString():String = {
    var str = "Vertex("  
    
    for(aType <- data.keySet){
      str += "["+aType.id+"]->"+data(aType)+","
    }
    
    str + ")";
  }
}

/**
 * Class that represents a single Face in a Mesh. This class is a more 
 * OO representation of a polygon, but it is slower than the Mesh and VertexData
 * representations. It is used mainly in simplifying Mesh operations
 */
class Polygon(val vertices:List[Vertex]){
  val vertsPerFace:Int = vertices.length
  
  /** Split the Polygon into a List of new Polygons. The first list is
   *  the new polygons on the negative side of the specified plane, and 
   *  the second list is the positive side.
   * 
   *  A Polygon's vertices will be considered on the plane if they are within
   *  minDis distance of the plane.
   *  
   *  Polygons with all vertices on the plane are placed in the negative side list.
   * 
   *  Polygons that share a side with the plane, but whose other vertices
   *  are all on the same side of the plane are placed on that side's list.
   * 
   *  Polygons that lie on both sides of the plane will be split into two 
   *  or more new Polygons along the intersecting line, such that their 
   *  vertsPerFace is the same as the split polygon. */
  def split(plane:Plane3F,minDist:Float):(List[Polygon],List[Polygon]) = {
    var verts:List[(Int,Float)] = Nil
    var posVerts,negVerts,planeVerts = 0;
    var i = 0;
    for(vertex <- vertices){
      val vert = vertex(ArrayType.Vertex)
      val dist = MathUtils.dist(vert,plane)
      verts = verts ::: List((i,dist))
      if(dist >= -minDist && dist <= minDist)
        planeVerts += 1
      else if(dist < 0.0f)
        negVerts += 1
      else
        posVerts += 1
      i += 1
    }
    
    if(negVerts + planeVerts == vertsPerFace){
      (this :: Nil , Nil)
    }
    else if(posVerts + planeVerts == vertsPerFace){
      (Nil, this :: Nil)
    }
    else{
      vertsPerFace match {
        case 2 => splitLine(plane)
        case 3 => splitTri(plane,verts,minDist)
        case 4 => splitQuad(plane,verts,minDist)
        case _ => (Nil,Nil)
      }
    }
  }
    
  private def splitLine(plane:Plane3F):(List[Polygon],List[Polygon]) = {
    val newVert = vertices(0).intersect(vertices(1),plane)
    (new Polygon(List(vertices(0),newVert)) :: Nil, new Polygon(List(newVert,vertices(1))) :: Nil)
  }
  
  private def splitTri(plane:Plane3F,verts:List[(Int,Float)],minDist:Float):(List[Polygon],List[Polygon]) = {
    //If there is a vertex on the plane, then split the triangle into two smaller triangles
    val planeVert = verts.find((i)=> {i._2 >= -minDist && i._2 <= minDist})
    if(planeVert.isDefined){
      val planeInd = planeVert.get._1
      val prevInd = wrap(planeInd - 1)
      val nextInd = wrap(planeInd + 1)
      val prevVert = vertices(prevInd)
      val nextVert = vertices(nextInd)
      
      val newVert = prevVert.intersect(nextVert,plane)
      var newTriData1:List[Vertex] = List(prevVert,vertices(planeInd),newVert)
      var newTriData2:List[Vertex] = List(newVert,vertices(planeInd),nextVert)

      if(verts(prevInd)._2 < 0.0f)
        (new Polygon(newTriData1)::Nil,new Polygon(newTriData2)::Nil)
      else
        (new Polygon(newTriData2)::Nil,new Polygon(newTriData1)::Nil)  
    }
    else{
      //If there is one vertex on the negative side of the plane, then create a triangle with an edge along the plane
      //and create two more triangles on the positive side of the plane
      val (singleInd,singleIsNegSide) = 
      if(verts.partition(_._2 < 0.0f)._1.size == 1)
	      (verts.find(_._2 < 0.0f).get._1,true)
      else
    	  (verts.find(_._2 > 0.0f).get._1,false)
      
      val prevInd = wrap(singleInd - 1)
      val nextInd = wrap(singleInd + 1)
      val singleVert = vertices(singleInd)
      val prevVert = vertices(prevInd)
      val nextVert = vertices(nextInd)
      val newPrevVert = singleVert.intersect(prevVert,plane)
      val newNextVert = singleVert.intersect(nextVert,plane)
      
      var singleTri:Polygon = new Polygon(List(newPrevVert,singleVert,newNextVert))
      var newTri1:Polygon = new Polygon(List(prevVert,newPrevVert,newNextVert))
      var newTri2:Polygon = new Polygon(List(newNextVert,nextVert,prevVert))
      
      if(singleIsNegSide)
        (singleTri::Nil,newTri1::newTri2::Nil)
	  else
        (newTri1::newTri2::Nil,singleTri::Nil)
    }
  }
  
  private def splitQuad(plane:Plane3F,verts:List[(Int,Float)],minDist:Float):(List[Polygon],List[Polygon]) = {
    (Nil,Nil)
  }
  
  
  private def wrap(index:Int):Int = {
    MathUtils.wrappedIndex(index,vertsPerFace)
  }
  
  override def equals(o:Any):Boolean = {
    o match{
      case p:Polygon => vertices == p.vertices
      case _ => false
    }
  }
  
  override def hashCode():Int = vertices.hashCode
  
  override def toString():String = {
    var str = "Face("
    for(vert <- vertices){
      str += vert
    }
    
    str+")"
  }
}
